/*
 * Created on April 25, 2007
 * 
 * Copyright (c) Patrick Armstrong 2002-2007. All Rights Reserved 
 */
package org.dime.mine.bot;

import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.dime.mine.game.Board;
import org.dime.mine.game.BoardUtil;

/**
 * AwareBoard // TODO Add type summary
 * 
 * @author Owner
 */
public class AwareBoard implements Board
{
    private final Board board;

    private Boolean isValid = null;

    /**
     * 
     */
    public AwareBoard(Board board)
    {
        this.board = board;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.dime.mine.game.Board#open(int, int)
     */
    public void open(int row, int column)
    {
        getLocation(row, column).open();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.dime.mine.game.Board#check(int, int)
     */
    public int check(int row, int column)
    {
        return board.check(row, column);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.dime.mine.game.Board#setMarked(int, int, boolean)
     */
    public void setMarked(int row, int column, boolean marked)
    {
        getLocation(row, column).setMarked(marked);
    }

    public List<AwareLocation> getNeighborhood(int row, int column)
    {
        return getAwareLocation(row, column).getNeighborhood();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.dime.mine.game.Board#isGameOver()
     */
    public boolean isGameOver()
    {
        return board.isGameOver();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.dime.mine.game.Board#isWon()
     */
    public boolean isWon()
    {
        return board.isWon();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.dime.mine.game.Board#isLost()
     */
    public boolean isLost()
    {
        return board.isLost();
    }

    public boolean isValid()
    {
        if (isValid == null)
            calculateValidity();

        return isValid;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.dime.mine.game.Board#getMines()
     */
    public int getMines()
    {
        return board.getMines();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.dime.mine.game.Board#getMarks()
     */
    public int getMarks()
    {
        return board.getMarks();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.dime.mine.game.Board#getRows()
     */
    public int getRows()
    {
        return board.getRows();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.dime.mine.game.Board#getColumns()
     */
    public int getColumns()
    {
        return board.getColumns();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.dime.mine.game.Board#getLocation(int, int)
     */
    public Location getLocation(int row, int column)
    {
        return getAwareLocation(row, column);
    }

    public AwareLocation getAwareLocation(int row, int column)
    {
        return new AwareLocation(row, column);
    }

    public Set<AwareLocation> getUnsolvedLocations()
    {
        Set<AwareLocation> set = getOpenLocations();
        Set<AwareLocation> set2 = new HashSet<AwareLocation>();
        for (AwareLocation loc : set)
        {
            if (loc.getUnknowns() != 0)
                set2.add(loc);
        }
        return set2;
    }

    public Board getBoard()
    {
        return board;
    }

    @Override
    public boolean equals(Object obj)
    {
        if (obj instanceof AwareBoard)
            return equals(((AwareBoard) obj).getBoard());
        else if (obj instanceof Board)
            return board.equals(obj);
        return false;
    }

    @Override
    public int hashCode()
    {
        return board.hashCode();
    }

    @Override
    public String toString()
    {
        return board.toString();
    }

    protected Set<AwareLocation> getOpenLocations()
    {
        HashSet<AwareLocation> set = new HashSet<AwareLocation>();
        AwareLocation loc;
        for (int i = 0; i < getRows(); i++)
        {
            for (int j = 0; j < getColumns(); j++)
            {
                loc = getAwareLocation(i, j);
                if (loc.isOpen())
                    set.add(loc);
            }
        }
        return set;
    }

    protected boolean isValidLocation(int row, int column)
    {
        return BoardUtil.isValidLocation(board, row, column);
    }

    protected void invalidateCalculations()
    {
        isValid = null;
    }

    protected void calculateValidity()
    {
        Iterator<AwareLocation> it = getOpenLocations().iterator();
        boolean flag = true;
        AwareLocation loc;

        while (flag && it.hasNext())
        {
            loc = it.next();
            flag &= loc.isValid();
            if (getMarks() == getMines())
                flag &= loc.isSatisfied();

        }

        isValid = new Boolean(flag);
    }

    public class AwareLocation implements Board.Location
    {
        private final Board.Location location;

        protected AwareLocation(int row, int column)
        {
            this.location = board.getLocation(row, column);
        }

        public Board getBoard()
        {
            return location.getBoard();
        }

        public int getColumn()
        {
            return location.getColumn();
        }

        public int getRow()
        {
            return location.getRow();
        }

        public int getValue()
        {
            return location.getValue();
        }

        public int getKnowns()
        {
            int knowns = 0;

            for (AwareLocation loc : getNeighborhood())
            {
                if (loc.isOpen())
                    knowns++;
            }

            return knowns;
        }

        public int getMarks()
        {
            int marks = 0;

            for (AwareLocation loc : getNeighborhood())
            {
                if (loc.isMarked())
                    marks++;
            }

            return marks;
        }

        public int getUnknowns()
        {
            int unknowns = 0;

            for (AwareLocation loc : getNeighborhood())
            {
                if (!loc.isMarked() && !loc.isOpen())
                    unknowns++;
            }

            return unknowns;
        }

        public List<AwareLocation> getNeighborhood()
        {
            return getNeighborhood(1);
        }

        public List<AwareLocation> getNeighborhood(int radius)
        {
            List<AwareLocation> neighbors = new LinkedList<AwareLocation>();
            for (int i = getRow() - radius; i <= getRow() + radius; i++)
            {
                for (int j = getColumn() - radius; j <= getColumn() + radius; j++)
                {
                    if (isValidLocation(i, j)
                            && !(i == getRow() && j == getColumn()))
                        neighbors.add(getAwareLocation(i, j));
                }
            }

            return neighbors;
        }

        public boolean isMarked()
        {
            return location.isMarked();
        }

        public boolean isOpen()
        {
            return location.isOpen();
        }

        public boolean isUnknown()
        {
            return !isOpen() && !isMarked();
        }

        /**
         * Tests whether this location is satisfied. A location is satisfied if
         * and only if it is open and the number of marks in its neighborhood is
         * equal to its value.
         * 
         * @return true if the location is satisfied, false otherwise
         */
        public boolean isSatisfied()
        {
            return location.isSatisfied();
        }

        /**
         * Tests whether this location has a valid mark configuration. A
         * location has a valid mark configuration if it is not open or if it is
         * open and its value is greater than or equal to the number of marks on
         * its border.
         * 
         * @return true if the location is valid, false otherwise
         */
        public boolean isValid()
        {
            if (isOpen())
                return getValue() >= getMarks();

            return true;
        }

        public void open()
        {
            location.open();
            invalidateCalculations();
        }

        public void setMarked(boolean marked)
        {
            location.setMarked(marked);
            invalidateCalculations();
        }

        @Override
        public String toString()
        {
            return location.toString();
        }

        @Override
        public boolean equals(Object obj)
        {
            return location.equals(obj);
        }

        @Override
        public int hashCode()
        {
            return location.hashCode();
        }
    }
}
